package org.lex.dbq.model.conf;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.lex.dbq.model.bo.Classpath;
import org.lex.dbq.model.bo.DefaultDriver;
import org.lex.dbq.model.bo.DriverConfig;
import org.lex.dbq.model.bo.JdbcUrlTemplate;
import org.lex.dbq.model.bo.Session;
import org.lex.dbq.model.bo.Variable;
import org.lex.dbq.model.service.BaseService;
import org.lex.dbq.model.service.ServiceFactory;
import org.lex.dbq.util.Constants;
import org.lex.io.utils.FileUtils;
import org.lex.lang.SystemUtils;
import org.lex.string.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ConfLoader {
	private static Logger log = LoggerFactory.getLogger(ConfLoader.class);

	public static void loadDrivers(String conf, BaseService<DriverConfig> drivers) {
		Document doc = readConfigXml(Conf.DRIVERS_CONF_FILE);
		if (null == doc || null == doc.getRootElement()) {
			log.warn("Driver conf not found");
			return;
		}

		Element root = doc.getRootElement();
		// load driver
		log.trace("begin load driver config");
		Element driversEle = root.element(Conf.Drivers);
		if (null != driversEle) {
			for (Element e : elements(driversEle, Conf.Driver)) {
				DriverConfig dc = new DriverConfig();
				dc.setId(e.attributeValue(Conf.id));
				dc.setName(text(e, Conf.Name));
				dc.setDescription(text(e, Conf.Description));
				dc.setWebsite(text(e, Conf.Website));
				dc.setClassName(text(e, Conf.ClassName));
				// classpath
				Classpath classpath = new Classpath();
				Element cpEle = e.element(Conf.ClassPath);
				for (Element locEle : elements(cpEle, Conf.Location)) {
					classpath.addLocation(new File(locEle.getText()));
				}
				dc.setClasspath(classpath);

				drivers.add(dc);
			}
		}
		log.info(drivers.size() + " Drivers loaded");
	}

	public static void storeDrivers(String conf, Collection<DriverConfig> drivers) throws IOException {
		Document doc = DocumentHelper.createDocument();
		Element rootEle = doc.addElement(Conf.DriverConfig);
		// drivers
		Element driversEle = rootEle.addElement(Conf.Drivers);
		for (DriverConfig e : drivers) {
			if (e.isDynamic()) {
				continue;
			}

			Element driverEle = driversEle.addElement(Conf.Driver);
			driverEle.addAttribute(Conf.id, e.getId());
			driverEle.addElement(Conf.Name).setText(e.getName());
			driverEle.addElement(Conf.ClassName).setText(e.getClassName());
			driverEle.addElement(Conf.Description).setText(e.getDescription());
			driverEle.addElement(Conf.Website).setText(e.getWebsite());
			// classpath
			Element cpEle = driverEle.addElement(Conf.ClassPath);
			for (File loc : e.getClasspath().getLocations()) {
				cpEle.addElement(Conf.Location).setText(loc.getAbsolutePath());
			}
		}

		// store
		ConfLoader.writeConfigXml(conf, doc);
	}

	public static void loadTemplates(String conf, BaseService<JdbcUrlTemplate> service) {
		Document doc = readConfigXml(conf);
		if (null == doc || null == doc.getRootElement()) {
			log.warn(conf + " not found!");
			return;
		}

		Element root = doc.getRootElement();
		for (Element e : elements(root, Conf.Template)) {
			JdbcUrlTemplate url = new JdbcUrlTemplate();
			url.setId(e.attributeValue(Conf.id));
			url.setName(text(e, Conf.Name));
			url.setDescription(text(e, Conf.Description));
			url.setTemplate(text(e, Conf.Template));
			// driverClassName
			String driverId = e.attributeValue(Conf.driverId);
			DriverConfig driver = ServiceFactory.getDriverService().getById(driverId);
			url.setDriver(driver);
			// variables
			for (Element varEle : elements(e, Conf.Variable)) {
				Variable var = new Variable();
				var.setRequired(Boolean.parseBoolean(varEle.attributeValue(Conf.required)));
				var.setSecret(Boolean.parseBoolean(varEle.attributeValue(Conf.secret)));
				var.setName(text(varEle, Conf.Name));
				var.setDescription(text(varEle, Conf.Description));
				var.setDisplayName(text(varEle, Conf.DisplayName));
				var.setDefaultValue(text(varEle, Conf.DefaultValue));

				url.addVadiable(var);
			}
			service.add(url);
		}
		log.info(service.size() + " Templates loaded");
	}

	public static void storeTemplates(String conf, Collection<JdbcUrlTemplate> templates) throws IOException {
		Document doc = DocumentHelper.createDocument();
		Element root = doc.addElement(Conf.TemplateConfig);
		for (JdbcUrlTemplate url : templates) {
			if (url.isDynamic()) {
				continue;
			}

			Element e = root.addElement(Conf.Template);
			e.addAttribute(Conf.id, url.getId());
			e.addAttribute(Conf.driverId, url.getDriver().getId());
			e.addElement(Conf.Name).setText(url.getName());
			e.addElement(Conf.Description).setText(url.getDescription());
			e.addElement(Conf.Template).setText(url.getTemplate());
			// variable
			for (Variable var : url.getVariables()) {
				Element varEle = e.addElement(Conf.Variable);
				varEle.addAttribute(Conf.required, Boolean.toString(var.isRequired()));
				varEle.addAttribute(Conf.secret, Boolean.toString(var.isSecret()));

				varEle.addElement(Conf.Name).setText(var.getName());
				varEle.addElement(Conf.Description).setText(var.getDescription());
				varEle.addElement(Conf.DisplayName).setText(var.getDisplayName());
				varEle.addElement(Conf.DefaultValue).setText(var.getDefaultValue());
			}
		}

		writeConfigXml(conf, doc);
	}

	public static void loadSessions(String conf, BaseService<Session> service) {
		Document doc = readConfigXml(conf);
		if (null == doc || null == doc.getRootElement()) {
			log.warn(conf + " not found!");
			return;
		}

		Element root = doc.getRootElement();
		for (Element e : elements(root, Conf.Session)) {
			Session s = new Session();
			s.setId(e.attributeValue(Conf.id));
			s.setName(text(e, Conf.Name));
			s.setDescription(text(e, Conf.Description));
			s.setUser(text(e, Conf.User));
			s.setPassword(text(e, Conf.Password));
			s.setUrl(text(e, Conf.URL));
			// driver
			String driverId = e.attributeValue(Conf.driverId);
			s.setDriver(ServiceFactory.getDriverService().getById(driverId));

			service.add(s);
		}
		log.info(service.size() + " Sessions loaded");
	}

	public static void storeSessions(String conf, Collection<Session> sessions) throws IOException {
		Document doc = DocumentHelper.createDocument();
		Element root = doc.addElement(Conf.SessionConfig);
		for (Session s : sessions) {
			Element e = root.addElement(Conf.Session);
			e.addAttribute(Conf.id, s.getId());
			e.addAttribute(Conf.driverId, s.getDriver().getId());
			e.addElement(Conf.Name).setText(s.getName());
			e.addElement(Conf.Description).setText(s.getDescription());
			e.addElement(Conf.User).setText(s.getUser());
			e.addElement(Conf.Password).setText(s.getPassword());
			e.addElement(Conf.URL).setText(s.getUrl());
		}

		writeConfigXml(conf, doc);
	}

	public static void loadDefaultDrivers(String classPath, Map<String, Set<DefaultDriver>> map) {
		// XXX
		InputStream is = ConfLoader.class.getResourceAsStream(classPath);
		// InputStream is = openFile(new File("D:\\tmp\\default_drivers.xml"));
		if (null == is) {
			log.warn("resource not found: " + classPath);
			return;
		}
		Document doc = parseXml(is);
		if (null == doc || null == doc.getRootElement()) {
			log.warn("resource is not xm: " + classPath);
			return;
		}
		int count = 0;
		for (Element ele : elements(doc.getRootElement(), "Driver")) {
			DefaultDriver dd = new DefaultDriver();

			dd.setName(text(ele, "name"));
			dd.setDriverClassName(text(ele, "driverClassName"));
			dd.setUrl(text(ele, "url"));
			dd.setWebsite(text(ele, "websiteUrl"));

			Set<DefaultDriver> set = map.get(dd.getDriverClassName());
			if (null == set) {
				set = new TreeSet<DefaultDriver>(DefaultDriver.NAME_COMPARATOR);
				map.put(dd.getDriverClassName(), set);
			}
			set.add(dd);
			count++;
		}
		log.info(count + " default drivers loaded");
	}

	public static void writeConfigXml(String conf, Document doc) throws IOException {
		String xml = XmlUtils.format(doc);
		if (log.isTraceEnabled()) {
			log.trace(conf + "\n" + xml);
		}
		File file = getConfigFile(conf);
		if (false == file.exists() || false == file.isFile() || false == file.canRead()) {
			boolean result = FileUtils.createFile(file);
			log.warn("conf " + file + " not exists, create success? " + result);
		}

		BufferedWriter bw = null;
		try {
			FileOutputStream fos = new FileOutputStream(file, false);
			OutputStreamWriter osw = new OutputStreamWriter(fos, Constants.CHARSET);
			bw = new BufferedWriter(osw);
			bw.write(xml);
			bw.flush();
		} finally {
			if (null != bw) {
				bw.close();
			}
		}
	}

	public static Document readConfigXml(String conf) {
		File file = ConfLoader.getConfigFile(conf);
		InputStream is = openFile(file);
		if (null != is) {
			return parseXml(is);
		}
		return null;
	}

	public static Document parseXml(InputStream is) {
		try {
			InputStreamReader isr = new InputStreamReader(is, Constants.CHARSET);
			BufferedReader br = new BufferedReader(isr);
			Document doc = new SAXReader().read(br);
			br.close();
			if (log.isTraceEnabled()) {
				log.trace("parse xml:\n" + XmlUtils.format(doc));
			}
			return doc;
		} catch (UnsupportedEncodingException e) {
			log.error("stream charset is not " + Constants.CHARSET, e);
		} catch (IOException e) {
			log.error("read stream failed", e);
		} catch (DocumentException e) {
			log.error("parse document failed", e);
		}
		return null;
	}

	public static FileInputStream openFile(File file) {
		try {
			FileInputStream fis = new FileInputStream(file);
			return fis;
		} catch (FileNotFoundException e) {
			log.error("file not found: " + file, e);
		}
		return null;
	}

	public static File getConfigFile(String conf) {
		File dir = new File(SystemUtils.getUserHome(), Conf.CONF_DIR);
		File file = new File(dir, conf);
		log.info("conf=" + conf + " is: " + file);
		return file;
	}

	private static List<Element> elements(Element parent, String name) {
		List<Element> list = parent.elements(name);
		if (null == list) {
			list = Collections.emptyList();
		}
		return list;
	}

	private static String text(Element parent, String name) {
		Element e = parent.element(name);
		if (null != e) {
			return e.getText();
		} else {
			return "";
		}
	}
}
